package com.app.admin.service.impl;

import com.app.admin.config.LockProperties;
import com.app.admin.mapper.SerialNumberMapper;
import com.app.admin.pojo.po.SerialNumber;
import com.app.admin.service.SerialNumberService;
import com.app.core.exception.DatabaseException;
import com.app.core.mvc.result.Code;
import com.app.core.mvc.result.CodeException;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.redisson.api.RedissonClient;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * 序列号表 服务实现类
 * 注：序列号生成服务可能存在多个线程同时竞争同一个值的情况，在此情况下，为了防止序列号重用的现象发生，采用分布式锁。
 * 首先获取锁的线程能够第一时间获取竞争的值，未获取到锁的线程则等待，等待超时后则获取失败，失败信息传递至上层。如果在等待时间内获取到锁则获取下
 * 一个值。后续线程均按此规律依次获取需要的值。
 *
 * @author qiangt
 * @version 1.0
 * @since 2022-09-15
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SerialNumberServiceImpl extends ServiceImpl<SerialNumberMapper, SerialNumber> implements SerialNumberService {

    /**
     * 锁的名称
     */
    private static final String LOCK_NAME = "SERIAL_NUMBER";
    /**
     * 默认等待时间
     */
    private static final long DEFAULT_WAIT_TIME = 6000;
    /**
     * 默认租赁时间
     */
    private static final long DEFAULT_LEASE_TIME = 12000;
    private final RedissonClient redissonClient;
    /**
     * YML锁配置封装类
     */
    private final LockProperties lockProperties;
    /**
     * 当前待获取自增序列号的锁配置，如果YML中没有配，则取默认值
     */
    private LockProperties.Config config;

    @Retryable(value = {InterruptedException.class, CodeException.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2))
    @Override
    public Long calculateUsableSerialNumber(final String type, final String group) {
        this.prepareSerialNumberLockConfig(type);
        boolean acquiredLock = false;
        try {
            acquiredLock = this.redissonClient
                    .getLock(this.getConfig().getLockKey())
                    .tryLock(this.getConfig().getWaitTime(), this.getConfig().getLeaseTime(), TimeUnit.MILLISECONDS);
            if (acquiredLock) {
                return this.doObtainUsableSerialNumber(type, group);
            } else {
                throw Code.A00018.toCodeException();
            }
        } catch (final InterruptedException e) {
            log.error("获取锁线程被中断：{}", e.getMessage(), e);
            throw Code.A00030.toCodeException();
        } catch (final CodeException e) {
            log.error("获取自增序列号失败：{}", e.getMessage(), e);
            throw e;
        } finally {
            if (acquiredLock) {
                this.redissonClient.getLock(this.getConfig().getLockKey()).unlock();
            }
        }
    }

    /**
     * 初始化锁配置，可能是yml中配置，可能是默认的配置
     *
     * @param type
     */
    private void prepareSerialNumberLockConfig(final String type) {
        this.config = Optional.ofNullable(this.lockProperties.getConfig())
                .map(map -> map.get(type)).orElse(createDefaultConfig());
    }

    /**
     * 获取当前锁类型的配置
     *
     * @return
     */
    private LockProperties.Config getConfig() {
        return this.config;
    }

    /**
     * 操作数据库，获取下一个自增序列号
     *
     * @param type  锁类型
     * @param group 锁分组，通常是yyyy-mm-dd
     * @return
     */
    private Long doObtainUsableSerialNumber(final String type, final String group) {
        final List<SerialNumber> serialNumberList = this.lambdaQuery()
                .select(SerialNumber.class, a -> true)
                .eq(SerialNumber::getSerialType, type)
                .eq(SerialNumber::getSerialGroup, group)
                .list();
        if (CollectionUtils.isNotEmpty(serialNumberList) && serialNumberList.size() > 1) {
            throw new DatabaseException("序列号数据异常：大于一条");
        }
        try {
            final SerialNumber serialNumber;
            // 补救措施：如果序列号记录还未初始化，则进行初始化
            if (CollectionUtils.isEmpty(serialNumberList)) {
                final SerialNumber insert = new SerialNumber();
                insert.setSerialGroup(group);
                insert.setSerialType(type);
                insert.setCurrentValue(0L);
                this.save(insert);
                serialNumber = insert;
            } else {
                serialNumber = serialNumberList.get(0);
            }
            final Long currentValue = serialNumber.getCurrentValue();
            final Long latestValue = currentValue + 1;
            serialNumber.setCurrentValue(latestValue);
            // 更新成功则返回，更新失败则重新获取
            if (this.lambdaUpdate()
                    .eq(SerialNumber::getSerialType, serialNumber.getSerialType())
                    .eq(SerialNumber::getSerialGroup, serialNumber.getSerialGroup())
                    .eq(SerialNumber::getCurrentValue, currentValue)
                    .set(SerialNumber::getCurrentValue, serialNumber.getCurrentValue())
                    .update()
            ) {
                return latestValue;
            } else {
                throw new DatabaseException(Code.A00029);
            }
        } catch (final Exception e) {
            throw new DatabaseException(Code.A00029, e.getMessage());
        }
    }

    /**
     * 创建默认的锁配置
     *
     * @return
     */
    private LockProperties.Config createDefaultConfig() {
        final LockProperties.Config defaultConfig = new LockProperties.Config();
        defaultConfig.setLockKey(LOCK_NAME);
        defaultConfig.setWaitTime(DEFAULT_WAIT_TIME);
        defaultConfig.setLeaseTime(DEFAULT_LEASE_TIME);
        return defaultConfig;
    }

}
